﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Data;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.DataGrid
{
  internal sealed class EditableCellViewModel : WeakViewModelBase
  {
    #region Constructor

    internal EditableCellViewModel( EditableRowViewModel row, Column column )
    {
      if( row == null )
        throw new ArgumentNullException( "row" );

      if( column == null )
        throw new ArgumentNullException( "column" );

      m_dataPath = row.DataPath;
      m_dataItem = row.DataItem;
      m_column = column;

      m_validationErrors = new ValidationErrorsObservableCollection<CellValidationError>();
      m_readOnlyValidationErrors = new ReadOnlyObservableCollection<CellValidationError>( m_validationErrors );
    }

    #endregion

    #region DataPath Property

    internal DataPath DataPath
    {
      get
      {
        return m_dataPath;
      }
    }

    private readonly DataPath m_dataPath;

    #endregion

    #region DataItem Property

    internal object DataItem
    {
      get
      {
        return m_dataItem;
      }
    }

    private readonly object m_dataItem;

    #endregion

    #region Column Property

    internal Column Column
    {
      get
      {
        return m_column;
      }
    }

    private readonly Column m_column;

    #endregion

    #region ValidationErrors Property

    internal ReadOnlyObservableCollection<CellValidationError> ValidationErrors
    {
      get
      {
        return m_readOnlyValidationErrors;
      }
    }

    private readonly ReadOnlyObservableCollection<CellValidationError> m_readOnlyValidationErrors;
    private readonly ValidationErrorsObservableCollection<CellValidationError> m_validationErrors;

    #endregion

    #region HasValidationErrors Property

    internal bool HasValidationErrors
    {
      get
      {
        return ( this.ValidationErrors.Count > 0 );
      }
    }

    #endregion

    #region HasRestrictiveValidationErrors Property

    internal bool HasRestrictiveValidationErrors
    {
      get
      {
        return this.ValidationErrors.Any( ( item ) => item.IsRestrictive );
      }
    }

    #endregion

    #region Value Property

    internal object Value
    {
      get
      {
        return m_repository.Content;
      }
      set
      {
        m_repository.SetContent( value );
      }
    }

    #endregion

    #region EditingState Property

    internal CellEditingState EditingState
    {
      get
      {
        return m_editingState;
      }
      set
      {
        if( value != m_editingState )
        {
          if( !Enum.IsDefined( typeof( CellEditingState ), value ) )
            throw new ArgumentException( "The EditingState is not defined.", "value" );

          m_editingState = value;

          this.RaisePropertyChanged( () => this.EditingState );
          this.RaisePropertyChanged( () => this.IsBeingEdited );
          this.RaiseKeepAliveChanged();
        }
      }
    }

    private CellEditingState m_editingState = CellEditingState.Unchanged;

    #endregion

    #region IsBeingEdited Property

    internal bool IsBeingEdited
    {
      get
      {
        var currentState = this.EditingState;

        return ( currentState == CellEditingState.Edited )
            || ( currentState == CellEditingState.Committing )
            || ( currentState == CellEditingState.Canceling );
      }
    }

    #endregion

    protected override bool StayAlive()
    {
      return ( this.EditingState != CellEditingState.Unchanged )
          || ( this.HasRestrictiveValidationErrors )
          || ( m_repository.IsDirty )
          || ( base.StayAlive() );
    }

    protected override void OnInitialize()
    {
      base.OnInitialize();

      m_validationErrors.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnValidationErrorsCollectionChanged );
      m_repository.PropertyChanged += new PropertyChangedEventHandler( this.OnRepositoryPropertyChanged );

      var validationErrorsCollection = ( INotifyCollectionChanged )m_repository.ValidationErrors;
      validationErrorsCollection.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnRepositoryValidationErrorsCollectionChanged );

      m_repository.SetContentBinding( this.GetContentBinding() );
    }

    protected override void OnClear()
    {
      base.OnClear();

      m_validationErrors.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnValidationErrorsCollectionChanged );
      m_repository.PropertyChanged -= new PropertyChangedEventHandler( this.OnRepositoryPropertyChanged );

      var validationErrorsCollection = ( INotifyCollectionChanged )m_repository.ValidationErrors;
      validationErrorsCollection.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnRepositoryValidationErrorsCollectionChanged );

      m_repository.Clear();
    }

    internal void CommitValue()
    {
      if( this.IsCleared )
        return;

      m_repository.UpdateSource();
    }

    internal void RollbackValue()
    {
      if( this.IsCleared )
        return;

      m_repository.RefreshContent();
    }

    private Binding GetContentBinding()
    {
      var binding = this.Column.DisplayMemberBinding;

      if( ( binding == null )
        || ( binding.Source != null )
        || ( binding.ElementName != null ) )
        return binding;

      if( binding.RelativeSource != null )
        throw new NotSupportedException( "Column bindings do not support the RelativeSource property." );

      var newBinding = new Binding();
      newBinding.BindsDirectlyToSource = binding.BindsDirectlyToSource;
      newBinding.Converter = binding.Converter;
      newBinding.ConverterCulture = binding.ConverterCulture;
      newBinding.ConverterParameter = binding.ConverterParameter;
      newBinding.FallbackValue = binding.FallbackValue;
      newBinding.Mode = binding.Mode;
      newBinding.NotifyOnValidationError = binding.NotifyOnValidationError;
      newBinding.Path = binding.Path;
      newBinding.StringFormat = binding.StringFormat;
      newBinding.TargetNullValue = binding.TargetNullValue;
      newBinding.UpdateSourceTrigger = binding.UpdateSourceTrigger;
      newBinding.ValidatesOnDataErrors = binding.ValidatesOnDataErrors;
      newBinding.ValidatesOnExceptions = binding.ValidatesOnExceptions;

#if SILVERLIGHT
      newBinding.ValidatesOnNotifyDataErrors = binding.ValidatesOnNotifyDataErrors;
#endif

      newBinding.Source = this.DataItem;

      return newBinding;
    }

    private void OnRepositoryPropertyChanged( object sender, PropertyChangedEventArgs e )
    {
      if( e.IsPropertyChanged( ( ContentRepository repository ) => repository.Content ) )
      {
        this.RaisePropertyChanged( () => this.Value );
      }

      if( e.IsPropertyChanged( ( ContentRepository repository ) => repository.IsDirty ) )
      {
        this.RaiseKeepAliveChanged();
      }
    }

    private void OnRepositoryValidationErrorsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      var validationErrors = ( IEnumerable<ValidationError> )sender;

      if( validationErrors.Any() || ( m_validationErrors.Count != 0 ) )
      {
        using( m_validationErrors.Batch() )
        {
          m_validationErrors.Clear();

          foreach( var validationError in validationErrors )
          {
            m_validationErrors.Add( new CellValidationError( this.DataPath, this.DataItem, this.Column, validationError ) );
          }
        }
      }
    }

    private void OnValidationErrorsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      this.RaisePropertyChanged( () => this.HasValidationErrors );
      this.RaisePropertyChanged( () => this.HasRestrictiveValidationErrors );
      this.RaiseKeepAliveChanged();
    }

    #region Private Fields

    private readonly ContentRepository m_repository = new ContentRepository();

    #endregion
  }
}
